/*-----------------------------------------------------------------------------------*/
//模拟解决线程同步互斥的问题（多个线程同时操作文件），通过互斥量、事件来解决，
//主要涉及的内容有多线程的创建，事件的创建，互斥量的创建, 线程的同步
//利用条件变量实现
// 
//设计学生成绩管理系统，主要包含: 
//输入记录模块.查询记录模块.更新记录模块.统计记录模块.输出记录模块

/*
* 操作流程:
*
* 有全局的数据,就得有与之配套的同步互斥操作,保证全局数据的数据一致性,不能出现有数据竞争导致的数据不一致的情况
* 定义全局的 vector<stu> vector_stu 与之配套的 mutex 和 condition_variable
* 定义全局的 g_file 与之配套的 mutex 和 condition_variable
*
* 数据先输入 vector_stu(得设置一个input_vector(int thread_id)函数)
* 如果有需要保存的话,再把vector_stu里面的数据保存进入文件 g_file 里面(得设置一个input_file(int thread_id)函数)
* 如果需要查询文件里面某一个的信息的话(得设置一个find_file(int thread_id, string name)函数)
* 如果需要打印文件里面所有人的信息的话(得设置一个output_file(int thread_id)函数)
* 如果需要知道文件里面多少有人的话(得设置一个cnt_file(int thread_id)函数)
*
*/

#define _CRT_SECURE_NO_WARNINGS 

#include <Windows.h>  // Sleep(1000); 需要的头文件 --> 这里的单位和linux里面的sleep(1)的单位不一样，这里是ms --> 1000ms == 1s
#include <iostream>
#include <stdio.h>
//#include <pthread.h>          // linux 平台(c语言)使用线程的头文件
#include <thread>               // windows 平台(cpp语言封装c语言库的线程)使用线程的头文件
#include <mutex>                // 互斥锁
#include <condition_variable>   // 条件变量

#include <string>
#include <vector>
#include <fstream>
#include <time.h>

using namespace std;

// 默认是 run == 1 是单线程。如果在修改 run != 1 的话，就是多线程
#define run 1
// 设置一个全局的条件编译用的变量 condition 为 1 的话，就是手动输入了
#define condition 1

// 全局的 stuv 数组,先输入进数组里面,如果要保存的话,再保存进文件里面
struct stu {
    string name = "";
    int age = 0;
    int yuweng = 0;
    int math = 0;
    int english = 0;
};
vector<stu> vector_stu;
// stuv 不能有数据竞争的情况
std::mutex vector_mtx;
std::condition_variable vector_cv;
bool vector_ready = true;

// 记录多个线程公共的文件 --> 因为有了共享资源 --> 数据竞争 --> 同步于互斥
const string g_file = "g_file.txt";
//const string g_file = "./g_file.txt";  // 这样写测试了，好像也是可以的
// g_file 不能有数据竞争的情况
std::mutex file_mtx;
std::condition_variable file_cv;
bool file_ready = true;

//菜单选择
void background() {
    cout << "----------1 --> 输入记录模块----------" << endl;
    cout << "----------2 --> 查询记录模块----------" << endl;
    cout << "----------3 --> 更新记录模块----------" << endl;
    cout << "----------4 --> 统计记录模块----------" << endl;
    cout << "----------5 --> 输出记录模块----------" << endl;
    //cout << "----------0 --> 退出----------" << endl;
}

// 1 输入记录模块
// 操作的是 vector_stu 数组(得要有操作的条件的哦)
void input_vector() {
	std::thread::id pthread_id = std::this_thread::get_id();
	cout << "线程" << pthread_id << "在执行任务" << endl;
 
	  //cout << "有人正在写入的情况下是不能先输入进 vector 里面的 " << endl;
    stu s;
    cout << "请输入 name age yuweng math engish 两两之间用空格隔开:";

#if(condition == 1)
    // 这个还是得我们自己输入，虽然模拟了几个线程，如果也随机输入的话，那大概是查不到的
    cin >> s.name >> s.age >> s.yuweng >> s.math >> s.english;
#else
    // 线程自己随机输入也是可以的
    int nums = rand() % 100 + 1;
    s.name = to_string(nums);

    int cnt = 4; 
    while (cnt--) {
        nums = rand() % 100 + 1;

        if (s.age == 0) s.age = nums;
        else if (s.yuweng == 0) s.yuweng = nums;
        else if (s.math == 0) s.math = nums;
        else s.english = nums;
    }
#endif

    vector_stu.push_back(s);

    printf("线程 %d 输入完成\n", pthread_id);
}

// 2 查询记录模块 --> 复用模块五的遍历代码
// 操作的是 g_file 文件(得要有操作的条件的哦)
bool find_file(string name, struct stu& s) {
    std::thread::id pthread_id = std::this_thread::get_id();
    cout << "线程" << pthread_id << "在执行任务" << endl;

    //cout << "有人正在写入的情况下是不能查询的 g_file 文件的" << endl;
    bool flag = false;

    std::ifstream in(g_file);
    if (in.is_open()) {
        std::string buffer;
        string find_name = "";
        while (std::getline(in, buffer)) {
            auto pos = buffer.find(' ');   
            if (pos == std::string::npos)
                cout << "debug" << endl; 
            find_name = buffer.substr(0, pos);
            if (find_name == name) {
                flag = true;
                break; 
            }
        }
        // 找到了,就把 inbuffer 给 s, 传递出去
        if (flag == true) {
            // 切割 string buffer = "name age yuweng math engish";
            s.name = name;
            for (int i = name.size() + 1; i < buffer.size(); i++) {
                string str = "";
                while (i < buffer.size() && buffer[i] != ' ')
                    str += buffer[i++];

                if (s.age == 0) s.age = stoi(str);
                else if (s.yuweng == 0) s.yuweng = stoi(str);
                else if (s.math == 0) s.math = stoi(str);
                else // (s.english == 0) 
                    s.english = stoi(str);
            }
        }

        in.close();
    }
    else std::cout << "Error opening file" << endl;

    return flag;
}

// 3 更新记录模块
// 操作的是 vector_stu 数组 和 g_file 文件(得要有操作的条件的哦)
void input_file() {
    std::thread::id pthread_id = std::this_thread::get_id();
    cout << "线程" << pthread_id << "在执行任务" << endl;

    //cout << "把vector里面的数据保存进 g_file 文件里面" << endl;
    ofstream out;
    out.open(g_file.c_str(), std::ofstream::out | std::ofstream::app);
    if (out.is_open()) {
        for (int i = 0; i < vector_stu.size(); i++) {
            string inbuffer = "";
            inbuffer += vector_stu[i].name;
            inbuffer += " ";
            inbuffer += to_string(vector_stu[i].age);
            inbuffer += " ";
            inbuffer += to_string(vector_stu[i].yuweng);
            inbuffer += " ";
            inbuffer += to_string(vector_stu[i].math);
            inbuffer += " ";
            inbuffer += to_string(vector_stu[i].english);
            inbuffer += " ";

            out << inbuffer.c_str() << endl;
        }

        vector_stu.clear();

        out.close();
    }
    else std::cout << "Error opening file" << endl;

    cout << "vector_stu 中的数据加载到文件 g_file 中已完成\n";
}

//4 统计记录模块 --> 复用模块五的遍历代码
// 操作的是 g_file 文件(得要有操作的条件的哦)
int cnt_file() {
    std::thread::id pthread_id = std::this_thread::get_id();
    cout << "线程" << pthread_id << "在执行任务" << endl;

    int cnt = 0;
    FILE* in = fopen(g_file.c_str(), "r");
    if (in != nullptr) {
        // 下面是正确的示范
        char inbuffer[1024];
        while (fgets(inbuffer, sizeof(inbuffer) - 1, in) != nullptr) 
            cnt++;  // 计数

        fclose(in);
    }
    else std::cout << "Error opening file" << endl;
    
    return cnt;
}

// 5 输出记录模块
// 操作的是 g_file 文件(得要有操作的条件的哦)
void output_file() {
    std::thread::id pthread_id = std::this_thread::get_id();
    cout << "线程" << pthread_id << "在执行任务" << endl;

    // 用 c 语言的方式,读取文件,一次读取一行
    FILE* in = fopen(g_file.c_str(), "r");
    if (in != nullptr) {
        // 下面是正确的示范
        char inbuffer[1024];
        while (fgets(inbuffer, sizeof(inbuffer) - 1, in) != nullptr) 
            cout << inbuffer;

        fclose(in);  
    }
    else std::cout << "Error opening file" << endl;
}



// 所有的用户(线程模拟)都需要跑这个函数
void* thread_run(void* args) {

    background();
    //system("cls");  
    //system("pause"); 

    int i = 0;
    do {
        cout << "请输入选项: ";
#if(condition == 1)
        cin >> i;  
#else
        i = rand() % 5 + 1;
#endif
        switch (i) {
        case 1:{
            // 输入记录模块
            // 操作的是 vector_stu 数组(得要有操作的条件的哦)
            //有人正在写入的情况下是不能输入(数据竞争)的 g_file 文件的

            std::unique_lock<std::mutex> lck(vector_mtx);
            while (!vector_ready) {
                vector_cv.wait(lck);
                cout << "线程正在等待资源中......" << endl;
            }

            input_vector();
            Sleep(1000);
        }
        break;
        case 2:{
            // 查询记录模块
            // 操作的是 g_file 文件(得要有操作的条件的哦)
            //有人正在写入的情况下是不能查询(数据竞争)的 g_file 文件的

            std::unique_lock<std::mutex> lck(file_mtx);
            while (!file_ready) {
                vector_cv.wait(lck);
                cout << "线程正在等待资源中......" << endl;
            }

            string name;
#if(condition == 1)
            // 这个还是得我们自己输入，虽然模拟了几个线程，如果也随机输入的话，那大概是查不到的
            cout << "请输入要查询的人的名字: ";
            cin >> name;
#else
            int nums = rand() % 100 + 1;
            name = to_string(nums);
#endif
            struct stu s;   
            bool flag = find_file(name, s);
            if (flag == true)
                cout << "找到了: " << s.name << ' ' << s.age << ' ' << s.yuweng << " " << s.math << ' ' << s.english << endl;
            else if (flag == false)
                cout << "没有找到这个人的信息" << endl;

            Sleep(1000);
        }
        break;
        case 3:{
            // 更新记录模块
            // 操作的是 vector_stu 数组 和 g_file 文件(得要有操作的条件的哦)

            std::unique_lock<std::mutex> file_lck(file_mtx);
            std::unique_lock<std::mutex> vector_lck(vector_mtx);
            while (!file_ready && !vector_ready) {
                vector_cv.wait(vector_lck);
                cout << "线程正在等待资源中......" << endl;
            }

            input_file();

            Sleep(1000);
        }
        break;
        case 4:{
            // 统计记录模块
            // 操作的是 g_file 文件(得要有操作的条件的哦)

            std::unique_lock<std::mutex> lck(file_mtx);
            while (!file_ready) {
                vector_cv.wait(lck);
                cout << "线程正在等待资源中......" << endl;
            }

            int cnt = cnt_file();
            // 看 cnt_file 函数的实现的话 --> 返回是 0 的话是有两种情况的哦
            printf("g_file 文件里面已经存储了 %d 个人的信息\n", cnt);

            Sleep(1000);
        }
        break;
        case 5:{
            // 输出记录模块
            // 操作的是 g_file 文件(得要有操作的条件的哦)

            std::unique_lock<std::mutex> lck(file_mtx);
            while (!file_ready) {
                vector_cv.wait(lck);
                cout << "线程正在等待资源中......" << endl;
            }

            output_file();

            Sleep(1000);
        }
        break;
        default:
            cout << "请输入[0, 5] 之间的整数!" << endl;
            break;
        }
    } while (i != 0);

    return nullptr;
}

int main(void) {
    srand((long int)time(nullptr));

#if(run == 1)
    int i = 1;
    thread t(thread_run, &i);
    t.join();

#else
    const int thread_cnt = 3;
    thread threads[thread_cnt];

    for (int i = 0; i < thread_cnt; i++)
        threads[i] = thread(thread_run, &i); 

    for (auto& th : threads) th.join();
#endif

    return 0;
}

/*
相关文章可百度：

推荐一篇关于多线程/多进程下有多把锁的情况下怎么防止死锁：
《多线程时需要注意互斥锁的加锁顺序，防止死锁》
https://blog.csdn.net/weixin_43354152/article/details/127365143

*/


